using UnityEngine;

namespace LS.SimpleInput
{
    public class GestureStateUtility
    {
        public static MoveDirection GetDirection(Vector2 start, Vector2 end, float directionTolerance = 0.85f)
        {
            Vector2 normal = (end - start).normalized;
            if (Vector2.Dot(normal, Vector2.up) >= directionTolerance)
                return MoveDirection.Up;

            if (Vector2.Dot(normal, -Vector2.up) >= directionTolerance)
                return MoveDirection.Down;

            if (Vector2.Dot(normal, Vector2.right) >= directionTolerance)
                return MoveDirection.Right;

            if (Vector2.Dot(normal, -Vector2.right) >= directionTolerance)
                return MoveDirection.Left;

            if (Vector2.Dot(normal, new Vector2(0.5f, 0.5f).normalized) >= directionTolerance)
                return MoveDirection.UpRight;

            if (Vector2.Dot(normal, new Vector2(0.5f, -0.5f).normalized) >= directionTolerance)
                return MoveDirection.DownRight;

            if (Vector2.Dot(normal, new Vector2(-0.5f, 0.5f).normalized) >= directionTolerance)
                return MoveDirection.UpLeft;

            if (Vector2.Dot(normal, new Vector2(-0.5f, -0.5f).normalized) >= directionTolerance)
                return MoveDirection.DownLeft;

            return MoveDirection.Other;
        }

        public static Gesture CreateGesture(GestureState state, Inputer inputer)
        {
            var ges = new Gesture()
            {
                inputId = inputer.InputId,
                deviceId = inputer.DeviceId,
                phase = state.Phase,
                gestureType = state.GestureType,
                eventType = EventType.None,
                moveDirection = MoveDirection.None,
                startTime = state.StartTime,
                deltaTime = state.DeltaTime,
                lastTime = state.LastTime,
                duration = state.Duration,
                eventSystemDetected = state.EventSystemDetected,
                isTriggerThisFrame = false,
                startPoint = state.StartPoint,
                point = state.Point,
                deltaPoint = state.DeltaPoint
            };

            if (state is TriggerGestureState tgs)
            {
                ges.isTriggerThisFrame = tgs.TriggerThisFrame;
            }

            if (state is MovementGestureState mgs)
            {
                ges.moveDirection = mgs.MoveDirection;
            }

            return ges;
        }

        public static void UpdateTapFrame(TriggerGestureState state, PointInputer inputer)
        {
            if (state.Active && inputer.Active && state.LastTime == inputer.LastTime)
            {
                Debug.LogWarning("GestureState don't need update.");
                return;
            }

            if (state.Active && inputer.Active && HandleTimeAnomaly(state, inputer))
            {
                return;
            }

            if (state.Phase == InputPhase.Canceled || state.Phase == InputPhase.Ended)
            {
                state.Phase = InputPhase.None;
                ResetTriggerGestureState(state);
                return;
            }

            if (inputer.Phase == InputPhase.Began)
            {
                state.TriggerThisFrame = false;
                state.Phase = InputPhase.Began;
                SetupStartValues(state, inputer);
            }
            else if (inputer.Phase == InputPhase.Stationary)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);
                    state.TriggerThisFrame = false;
                    if (state.Duration < inputer.Setting.longTapTime)
                        state.Phase = InputPhase.Stationary;
                    else
                        state.Phase = InputPhase.Canceled;
                }
            }
            else if (inputer.Phase == InputPhase.Moved)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);
                    state.Phase = InputPhase.Canceled;
                    state.TriggerThisFrame = false;
                }
            }
            else if (inputer.Phase == InputPhase.Ended)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);
                    if (state.Duration < inputer.Setting.longTapTime)
                    {
                        state.TriggerThisFrame = true;
                        state.Phase = InputPhase.Ended;
                    }
                    else
                    {
                        state.TriggerThisFrame = false;
                        CancleGestureState(state);
                    }
                }
            }
            else if (inputer.Phase == InputPhase.Canceled)
            {
                UpdateGenericValues(state, inputer);
                state.Phase = InputPhase.Canceled;
                state.TriggerThisFrame = false;
            }
        }

        public static void UpdateLongTapFrame(TimerGestureState state, PointInputer inputer)
        {
            if (state.Active && inputer.Active && state.LastTime == inputer.LastTime)
            {
                Debug.LogWarning("GestureState don't need update.");
                return;
            }

            if (state.Active && inputer.Active && HandleTimeAnomaly(state, inputer))
            {
                return;
            }

            UserInputSetting setting = inputer.Setting;
            float longTapTime = setting.longTapTime;
            LongTapPriority longTapPriority = setting.longTapPriority;

            if (state.CancledThisFrame || state.EndThisFrame)
            {
                ResetTimerGestureState(state);
                state.Phase = InputPhase.None;
                return;
            }

            if (inputer.BeganThisFrame)
            {
                SetupStartValues(state, inputer);
                state.Phase = InputPhase.Began;
                state.MinimumDuration = longTapTime;
                state.TimerBegining = false;
                state.TimerBeginingThisFrame = false;
                if(longTapPriority == LongTapPriority.LongTap)
                {
                    state.TimerBegining = true;
                    state.TimerBeginingThisFrame = true;
                    state.TimerBeginingTime = state.LastTime;
                }
            }
            else if (inputer.Stationary)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);
                    state.Phase = InputPhase.Stationary;

                    if (longTapPriority == LongTapPriority.Tap)
                    {
                        if(state.Duration < longTapTime)
                        {
                            state.TimerBegining = false;
                            state.TimerBeginingThisFrame = false;
                        }
                        else
                        {
                            if (!state.TimerBegining)
                            {
                                state.TimerBeginingThisFrame = true;
                                state.TimerBeginingTime = state.LastTime;
                            }
                            else
                                state.TimerBeginingThisFrame = false;
                            
                            state.TimerBegining = true;
                        }
                    }
                    else
                    {
                        state.TimerBegining = true;
                        state.TimerBeginingThisFrame = false;
                    }
                }
            }
            else if (inputer.Moving)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);
                    state.Phase = InputPhase.Canceled;
                    state.TimerBeginingThisFrame = false;
                }
            }
            else if (inputer.EndThisFrame)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);
                    state.TimerBeginingThisFrame = false;
                    if (state.Duration < longTapTime)
                        state.Phase = InputPhase.Canceled;
                    else
                        state.Phase = InputPhase.Ended;
                }
            }
            else if (inputer.Phase == InputPhase.Canceled)
            {
                UpdateGenericValues(state, inputer);
                state.Phase = InputPhase.Canceled;
                state.TimerBeginingThisFrame = false;
            }
        }

        public static void UpdateDoubleTapFrame(DoubleTriggerGestureState state, PointInputer inputer)
        {
            if (state.Active && inputer.Active && state.LastTime == inputer.LastTime)
            {
                Debug.LogWarning("GestureState don't need update.");
                return;
            }

            if (state.Active && inputer.Active && HandleTimeAnomaly(state, inputer))
            {
                return;
            }

            if (state.Phase == InputPhase.Canceled)
            {
                ResetDoubleTriggerGestureState(state);
                state.Phase = InputPhase.None;
            }
            else if (state.Phase == InputPhase.Ended)
            {
                ResetDoubleTriggerGestureState(state, state.InDoubleTrigger);
                state.Phase = InputPhase.None;
            }

            if (inputer.Phase == InputPhase.Began)
            {
                SetupStartValues(state, inputer);
                state.Phase = InputPhase.Began;
                state.TriggerThisFrame = false;
                state.MinimumDoubleTriggerDuration = inputer.Setting.doubleTapTime;
            }
            else if (inputer.Phase == InputPhase.Stationary)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);

                    if (state.Duration < inputer.Setting.longTapTime)
                    {
                        state.Phase = InputPhase.Stationary;
                        state.TriggerThisFrame = false;
                    }
                    else
                        state.Phase = InputPhase.Canceled;
                }
            }
            else if (inputer.Phase == InputPhase.Moved)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);
                    state.Phase = InputPhase.Canceled;
                    state.TriggerThisFrame = false;
                }
            }
            else if (inputer.Phase == InputPhase.Ended)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);
                    if (state.Duration < inputer.Setting.longTapTime)
                    {
                        state.Phase = InputPhase.Ended;
                        if (!state.InDoubleTrigger)
                        {
                            state.TriggerThisFrame = false;
                            state.InDoubleTrigger = true;
                            state.LastTriggerTime = state.LastTime;
                        }
                        else if (state.LastTime - state.LastTriggerTime < state.MinimumDoubleTriggerDuration)
                        {
                            state.TriggerThisFrame = true;
                            state.InDoubleTrigger = false;
                            state.LastTriggerTime = 0;
                        }
                        else
                        {
                            state.TriggerThisFrame = false;
                            state.InDoubleTrigger = true;
                            state.LastTriggerTime = state.LastTime;
                        }
                    }
                    else
                    {
                        state.Phase = InputPhase.Canceled;
                    }
                }
            }
            else if (inputer.Phase == InputPhase.Canceled)
            {
                UpdateGenericValues(state, inputer);
                state.Phase = InputPhase.Canceled;
                state.TriggerThisFrame = false;
            }

            if (state.InDoubleTrigger)
            {
                if (Time.unscaledTime - state.LastTime >= inputer.Setting.doubleTapTime)
                {
                    if (!state.Active)
                    {
                        state.InDoubleTrigger = false;
                        state.LastTriggerTime = 0;
                    }
                }
            }
        }

        public static void UpdateSwipeFrame(MovementGestureState state, PointInputer inputer)
        {
            if (state.Active && inputer.Active && state.LastTime == inputer.LastTime)
            {
                Debug.LogWarning("GestureState don't need update.");
                return;
            }

            if (state.Active && inputer.Active && HandleTimeAnomaly(state, inputer))
            {
                return;
            }

            if (state.Phase == InputPhase.Canceled || state.Phase == InputPhase.Ended)
            {
                ResetMovementGestureState(state);
                state.Phase = InputPhase.None;
                return;
            }

            if (inputer.Phase == InputPhase.Began)
            {
                SetupStartValues(state, inputer);
                state.Phase = InputPhase.Began;
                state.MoveDirection = MoveDirection.None;
            }
            else if (inputer.Phase == InputPhase.Stationary)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);
                    state.Phase = InputPhase.Stationary;
                }
            }
            else if (inputer.Phase == InputPhase.Moved)
            {
                if (state.Effective)
                {
                    UpdateGenericValues(state, inputer);
                    state.Phase = InputPhase.Moved;
                    state.MoveDirection = GetDirection(Vector2.zero, state.DeltaPoint, inputer.Setting.movementDirectionTolerance);
                }
            }
            else if (inputer.Phase == InputPhase.Ended)
            {
                if (state.Effective)
                {
                    InputPhase previousPhase = state.Phase;
                    UpdateGenericValues(state, inputer);
                    if (previousPhase == InputPhase.Stationary)
                    {
                        state.Phase = InputPhase.Canceled;
                    }
                }
            }
            else if (inputer.Phase == InputPhase.Canceled)
            {
                UpdateGenericValues(state, inputer);
                state.Phase = InputPhase.Canceled;
                
            }
        }

        public static void SetupStartValues(GestureState state, PointInputer inputer)
        {
            state.StartTime = inputer.StartTime;
            state.LastTime = inputer.LastTime;
            state.DeltaTime = inputer.DeltaTime;
            state.Duration = inputer.Duration;
            state.EventSystemDetected = inputer.EventSystemDetected;
            state.StartPoint = inputer.StartPoint;
            state.Point = inputer.Point;
            state.DeltaPoint = inputer.DeltaPoint;
        }

        public static void UpdateGenericValues(GestureState state, PointInputer inputer)
        {
            state.Point = inputer.Point;
            state.DeltaPoint = inputer.DeltaPoint;
            state.LastTime = inputer.LastTime;
            state.DeltaTime = inputer.DeltaTime;
            state.Duration = inputer.Duration;
        }

        public static void CancleGestureState(GestureState state)
        {
            state.Phase = InputPhase.Canceled;
        }

        public static void ResetMovementGestureState(MovementGestureState state)
        {
            state.MoveDirection = MoveDirection.None;
            ResetGestureState(state);
        }

        public static void ResetDoubleTriggerGestureState(DoubleTriggerGestureState state, bool keepDoubleState = false)
        {
            if (!keepDoubleState)
            {
                state.InDoubleTrigger = false;
                state.LastTriggerTime = 0;
            }
            state.MinimumDoubleTriggerDuration = 0;
            ResetTriggerGestureState(state);
        }

        public static void ResetTriggerGestureState(TriggerGestureState state)
        {
            state.TriggerThisFrame = false;
            ResetGestureState(state);
        }

        public static void ResetTimerGestureState(TimerGestureState state)
        {
            state.MinimumDuration = 0;
            state.TimerBeginingTime = 0;
            state.TimerBegining = false;
            state.TimerBeginingThisFrame = false;
            ResetGestureState(state);
        }

        public static void ResetGestureState(GestureState state)
        {
            state.Phase = InputPhase.None;
            state.StartTime = 0;
            state.DeltaTime = 0;
            state.LastTime = 0;
            state.Duration = 0;
            state.EventSystemDetected = false;
            state.StartPoint = Vector2.zero;
            state.Point = Vector2.zero;
            state.DeltaPoint = Vector2.zero;
        }

        private static bool HandleTimeAnomaly(GestureState state, PointInputer inputer)
        {
            if (state.LastTime > inputer.LastTime)
            {
                Debug.LogError("Time anomaly. This GestureState will be forced cancellation.");
                UpdateGenericValues(state, inputer);
                CancleGestureState(state);
                return true;
            }
            return false;
        }

    }
}
